﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Skugo.Core;
using System.Windows.Threading;
using AvalonDock;
using System.ComponentModel;
using Microsoft.Win32;
using System.Xml;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using Skugo.Shared;

namespace Driver
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// The current project we are connected to.
        /// </summary>
        private ProjectClient Client = null;

        /// <summary>
        /// A local process that we started (or null if we started nothing).
        /// </summary>
        private Process LocalProcess = null;

        private Job ParentJob = new Job();

        /// <summary>
        /// Handles reporting of crashes.
        /// </summary>
        private CrashHandler CrashReporter;

        /// <summary>
        /// A timer that we use to update the framework.
        /// </summary>
        private DispatcherTimer Dispatch = new DispatcherTimer();

        public MainWindow()
        {
            // Initializer our crash handler
            if (Debugger.IsAttached == false)
            {
                this.CrashReporter = new CrashHandler();
            }

            // Load all the plugins from side-by-side dlls
            PluginFactory.LoadPlugins();

            // Hook up a timer that updates the skugo framework
            this.Dispatch.IsEnabled = true;
            this.Dispatch.Interval = TimeSpan.FromSeconds(0.01);
            this.Dispatch.Tick += this.OnUpdate;

            // Change the theme to a more visual studio style
            ThemeFactory.ChangeTheme(new Uri("/AvalonDock.Themes;component/themes/dev2010.xaml", UriKind.RelativeOrAbsolute));

            // Initialize all the form components
            InitializeComponent();

            // Default to the loopback address
            this.Host.Text = IPAddress.Loopback.ToString();

            // Finally, set the port text box to the default skugo port
            this.Port.Value = Properties.Settings.Default.Port;

            // Load the settings for the executable file
            this.ExecutableFile.Text = Properties.Settings.Default.ExecutableFile;

            // Set the arguments that we saved
            this.ExtraArguments.Text = Properties.Settings.Default.Arguments;

            // Read the config xml file (should be side by side with the exe, usually created by the user)
            var config = new XmlTextReader("Config.xml");

            try
            {
                // Read through the xml file
                while (config.Read())
                {
                    // If we found the application tag...
                    if (config.Name == "executable")
                    {
                        // Read the application path
                        config.Read();
                        var executable = config.ReadContentAsString();

                        // Check if the file exists
                        if (File.Exists(executable))
                        {
                            // Load the settings for the executable file
                            this.ExecutableFile.Text = executable;
                        }
                        else
                        {
                            // Show an error message
                            MessageBox.Show("Sorry, but the executable specified in the Config.xml file did not seem to exist:\n" +
                                executable, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                        }
                    }
                    // If we found the host tag...
                    else if (config.Name == "host")
                    {
                        // Read the application path
                        config.Read();
                        this.Host.Text = config.ReadContentAsString();
                    }
                    // If we found the port tag...
                    else if (config.Name == "port")
                    {
                        // Read the application path
                        config.Read();
                        this.Port.Value = config.ReadContentAsInt();
                    }
                    // If we found the arguments tag...
                    else if (config.Name == "arguments")
                    {
                        // Read the application path
                        config.Read();
                        this.ExtraArguments.Text = config.ReadContentAsString();
                    }
                }
            }
            catch (FileNotFoundException)
            {
            }
            catch (Exception e)
            {
                // Show an error to the user
                MessageBox.Show("Sorry, I can't seem to read the config file because:\n" + e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void AttemptCloseProject()
        {
            if (this.Client != null)
            {
                // Clear out the client (disconnects all events, and also closes the socket)
                this.Client.Dispose();
                this.Client = null;
            }

            // If we have a local process running...
            if (this.LocalProcess != null)
            {
                try
                {
                    // Close the process down
                    this.LocalProcess.CloseMainWindow();
                    this.LocalProcess.WaitForExit(500);
                    this.LocalProcess.Kill();
                    this.LocalProcess = null;
                }
                catch
                {
                }
            }
        }

        private void OnDisconnected(Client client)
        {
            ProjectClient.AttemptSaveLayout(this.Client);

            // Show the connect dialog
            this.Area.Children.Remove(this.Client.Dock);
            this.Area.Children.Add(this.ConnectDialog);

            // Close the project and destroy the client
            this.AttemptCloseProject();

            // Update the status bar...
            this.ChangeStatus("Disconnected", false);
        }

        private void OnConnectionFailed(Client client)
        {
            // Close the project
            this.AttemptCloseProject();

            // Update the status bar...
            this.ChangeStatus("Connection Failed :(", false);
        }

        private void OnConnected(Client client)
        {
            // Hide the connect dialog and show the project interface
            this.Area.Children.Remove(this.ConnectDialog);
            this.Area.Children.Add(this.Client.Dock);

            // Update the status bar...
            this.ChangeStatus("Connected!", false);
        }

        private void OnUpdate(Object sender, EventArgs e)
        {
            // If we have a project...
            if (this.Client != null)
            {
                // Updates the project (which sends and receives data)
                this.Client.Update();
            }
        }

        private void Window_Loaded(Object sender, RoutedEventArgs e)
        {
            // Give access to core elements (such as the window/dock)
            Framework.Main = this;
        }

        private void ChangeStatus(String text, Boolean lockUi)
        {
            // Update the status bar...
            this.Connect.IsEnabled = !lockUi;
            this.Host.IsEnabled = !lockUi;
            this.Port.IsEnabled = !lockUi;
            this.RunLocally.IsEnabled = !lockUi;
            this.ExecutableFile.IsEnabled = !lockUi;
            this.BrowseForExeFile.IsEnabled = !lockUi;
            this.ExtraArguments.IsEnabled = !lockUi;
            this.DebugMode.IsEnabled = !lockUi;
            this.Status.Text = text;
            this.Progress.IsIndeterminate = lockUi;
        }

        private void Connect_Click(Object sender, RoutedEventArgs e)
        {
            // Create the project
            this.Client = new ProjectClient();

            // We want to know when the client changes state
            this.Client.OnConnected += this.OnConnected;
            this.Client.OnConnectionFailed += this.OnConnectionFailed;
            this.Client.OnDisconnected += this.OnDisconnected;

            // When we click connect, tell the client to connect
            this.Client.Connect(this.Host.Text, (UInt16)this.Port.Value);
            
            // Update the status bar...
            this.ChangeStatus("Connecting...", true);
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            // Attempt to save the layout since we're closing out
            ProjectClient.AttemptSaveLayout(this.Client);

            // Close the project
            this.AttemptCloseProject();
        }

        private void BrowseForExeFile_Click(object sender, RoutedEventArgs e)
        {
            // Create an open file dialog to search for an executable
            var dialog = new OpenFileDialog();
            dialog.Filter = "Application (*.exe)|*.exe|All Files (*.*)|*.*";

            // Show the dialog, if we succeed...
            if (dialog.ShowDialog() == true)
            {
                this.ExecutableFile.Text = dialog.FileName;
            }
        }

        private void ExecutableFile_TextChanged(object sender, TextChangedEventArgs e)
        {
            // Save the settings for the executable file
            Properties.Settings.Default.ExecutableFile = this.ExecutableFile.Text;
            Properties.Settings.Default.Save();
        }

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        private void RunLocally_Click(object sender, RoutedEventArgs e)
        {
            // Set the ip to be local
            this.Host.Text = IPAddress.Loopback.ToString();

            try
            {
                // Setup the information to start the process
                var info = new ProcessStartInfo()
                {
                    FileName = System.IO.Path.GetFullPath(this.ExecutableFile.Text),
                    Arguments = this.ExtraArguments.Text + " -SkugoLocal:" + this.Port.Value,
                    UseShellExecute = false,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    WorkingDirectory = new FileInfo(this.ExecutableFile.Text).Directory.FullName,
                    CreateNoWindow = true,
                    WindowStyle = ProcessWindowStyle.Hidden,
                };

                // Locally start the process
                this.LocalProcess = Process.Start(info);
                this.LocalProcess.PriorityBoostEnabled = false;
                this.LocalProcess.WaitForInputIdle();

                // Add the process to our job so that it gets closed when we exit
                this.ParentJob.AddProcess(this.LocalProcess.Handle);

                // If we want to launch the process with debugging...
                if (this.DebugMode.IsChecked == true)
                {
                    // Get the process id and attach a debugger to it
                    //this.AttachToProcess(this.LocalProcess.Id);
                }
            }
            catch (Exception ex)
            {
                // Show an error message
                MessageBox.Show("Sorry, I can't seem to start the executable because:\n" + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                this.LocalProcess = null;
                return;
            }
            
            // Sleep for a bit before we try and connect
            Thread.Sleep(100);

            // Click the connect button
            this.Connect_Click(sender, e);
        }

        private void Port_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            // Save the settings for the executable file
            Properties.Settings.Default.Port = (UInt16)this.Port.Value;
            Properties.Settings.Default.Save();
        }

        private void ExtraArguments_TextChanged(object sender, TextChangedEventArgs e)
        {
            // Save the settings for the arguments file
            Properties.Settings.Default.Arguments = this.ExtraArguments.Text;
            Properties.Settings.Default.Save();
        }
    }
}
